This is an incomplete description of the Quake Network Protocol used in Quake version 1.01. The protocol used in Quake Worlds will probably be rather different, and hopefully much more optimised.
The network protocol described below is not derived from documents by id software, so it's likely to be incomplete (or even wrong).
The messages exchanged by the Quake client and servers are not really complex to understand, but they are quite numerous, and of heterogeneous structure. Please read the example of network session, before trying to emulate a Quake client or server.
Since the messages in the .DEM files have exactly the same format as some of the network protocol messages, it is highly recommended that you also take a look at the DEM specs by Uwe Girlich.
When describing the content of messages, we will use the same conventions as in the DEM specs:
ReadString() = read a sequence of characters, terminated by NULL ('\0') ReadAngle() = read a byte, and multiply it by 360.0 / 256.0 to make it into degrees. ReadChar() = read a signed integer, on one byte. ReadShort() = read a signed integer, on two bytes, Big Endian order (Intel order). ReadLong() = read a signed integer, on four bytes, Big Endian order (Intel order). ReadFloat() = read a floating point number, on four bytes, Big Endian order (Intel order).
The structure of control messages is the following:
typedef struct { short Type=0x8000; // (Little Endian) Type of message. short Length; // (Little Endian) Length of message, header included. char message[Length-4]; // The contents of the message. } MSGHEADER;A control message, as opposed to a more ordinary game message, if characterised by the fact that Type & 0x8000 is not zero (which means that bit 15 is always set on a control message).
Warning: the fields Type and Length are in Little Endian order (most significant byte first). To read them on an Intel processor, you must swap the higher and lower bytes.
The structure of the message fields is: a single-byte code, followed by code-dependant informations. The following table details this information.
Thanks to A.Oliver for the description of the control requests 0x3, 0x4, 0x82, 0x84, 0x85 in the Unofficial Quake Network Protocol Specs 1.01.
Messages | Code | Data |
---|---|---|
Connection Request | 0x01 | GameName = ReadString(); // = "QUAKE" ProtocolVersion = ReadChar(); // = 3 |
Request Server Information | 0x02 | GameName = ReadString(); // = "QUAKE" ProtocolVersion = ReadChar(); // = 3 |
Request Player Information | 0x03 | Player = ReadChar(); // player number, from 0 to 15 |
Request Rule Information | 0x04 | Rule = ReadString(); // name of the previous rule // To pool all rule names, start with Rule = "", then continue // with the rule name obtained in the reply, // until an empty reply is received. |
Accept Connection | 0x81 | Port = ReadLong(); // Client's personal UDP or IPX port number |
Reject Connection | 0x82 | Reason = ReadString(); // Reason for rejecting the request |
Give Server Informations | 0x83 | Address = ReadString(); // Server address, in ascii Hostname= ReadString(); // name of the host machine Mapname = ReadString(); // name of the current map Players = ReadChar(); // current number of players Maximum = ReadChar(); // maximum number of players ProtocolVersion = ReadChar(); // = 3 |
Give Player Informations | 0x84 | Player = ReadChar(); // player number, from 0 to 15 Name = ReadString(); // name of the player Colors = ReadChar(); // shirt/pants colors ReadChar(); // zero ??? ReadChar(); // zero ??? ReadChar(); // zero ??? frags = ReadLong(); // Number of frags time = ReadLong(); // Connection time in seconds Address = ReadString(); // ip address and port, or "LOCAL". |
Give Rule Informations | 0x85 | // If this message is void, there are no more rules. Rule = ReadString(); // Name of the rule Value = ReadString(); // Ascii value of the requested rule // Rule is one of "sv_maxspeed", "sv_friction", "sv_gravity", // "noexit", "teamplay", "timelimit" or "fraglimit". |
Note: the control messages contain only one message part (one single code, followed by code-dependant information). But there could be many parts, though it has never been observed.
Every network packet send via UDP (and probably IPX) has the same header:
typedef struct { short Type; // (Little Endian) Type of message. short Length; // (Little Endian) Length of message, header included. long Order; // (Little Endian) Order of emission. char message[Length-8]; // The contents of the message. } MSGHEADER;A game message, as opposed to a Control message, is characterised by the expression (Type & 0x8000) == 0.
Warning: The fields Type, Length and Order are in Little Endian order (most significant byte first). To read them on an Intel processor, you must swap the higher and lower bytes.
Type | Meaning | Explanations |
---|---|---|
0x01 | Part of a Reliable Message | This message must be acknowledged. When message that doesn't fit into a single network packet must be sent, it is split in as many parts as necessary. Each of these parts will be sent in order, as message type 0x01, except the last part that will be sent as a message type Ox09. |
0x02 | Acknowledge of a Reliable Message | This message is sent by the recipient of a message type 0x01 or 0x09, to confirm that the message was received correctly. |
0x09 | End of a Reliable Message | This message must be acknowledged. This is just like message type 0x01, except that once the message is received, the whole message buffer can be interpreted. Note that if the message to be sent is small enough to fit into a single network packet, then it will be sent as a single message type 0x09. |
0x10 | Update Message | This message must not be acknowledged. The updates message are made of a single packet, and are usually very short (about 20 to 200 bytes). |
The message content is made of a single byte code, followed by some code-dependent data, and then followed by another byte code, and so on until the end of the message.
That means you need to know how to interpret all the possible byte codes to read a single message. That also means that if you make a mistake in the encoding of a single code, then all the rest will be screwed up, and can eventually crash the game. So trust it on luck!
To make matter worse, the byte codes must be interpreted differently, wether the message comes from a game Server or from a game Client.
This is the format of the message field, for messages sent by a game client. This field is an unstructured set of bytes, that must be read one by one in order to decode all the parts of the message.
First, you should read one byte (the message code). Then, depending, on this code, you must read zero or more bytes, depending on the code. That gives you one prt of the message. To read the next part, read one byte, that gives you a code, and so on... until the end of the message field.
Stop at the first unrecognised code, it's no use to continue if you have lost your marks.
Code | Message | Informations | Explanations |
---|---|---|---|
0x00 | No Operations | Void | This message indicates an error. |
0x01 | Keep Alive | Void | This message is sent when the client is iddle, but the connection should not be dropped. |
0x02 | Disconnect | Void | This message is sent when the player has left the game, and the connection should be dropped. This is the last message sent by a client. |
0x03 | Client Movement | ActionTime= ReadFloat(); TiltAngle = ReadAngle(); YawAngle = ReadAngle(); FlipAngle = ReadAngle(); SpeedFront = ReadInt16(); SpeedRight = ReadInt16(); SpeedUp = ReadInt16(); Flag = ReadChar(); // Flag & 1: fire. // Flag & 2: jump. Impulse = ReadChar(); // Impulse command | This message is sent when the player tries to move. All fields (except the angles) are zero by default, if the player doesn't move. The action time is usually some time in the near past, when the command was issued. Action messages are often repeated with the same action time, and then they all count for one message. The angles and speed are those desired by the player, but maybe not those that the physic of the worl will allow. The player's angle, position and velocity are controlled by the game server, not by the client, so the consequences of a given movement attempt will only be known when the server sends an update. |
0x04 | Console order |
Command = ReadString() | This message is sent by the client, when it must send an order on
the console. Those messages are used both for hidden game startup commands, and for the orders that a player will type on the console (like say xxx or say_team xxx). |
others | Unknown | Void | Those codes seem to be useless. |
The angles transmitted by a player movements are directly the angles set by the player, whether via the mouse or via the look up/down keys. Note that there is a flip angle indicated, but since no player command can possibly act on it, it's always zero. Too bad... but this is not DESCENT.
The speed are ordinary signed integers, that seem to indicate a desired speed in units per second. Experiment shows that when the player places an order for +200 forward speed (typical run order), then the player accelerate, until the speed stabilises at +200 coordinates per second (which makes it 10 coordinates per update, that happen every 20th of second).
This behavior proves that this parameter is a speed, not an acceleration. Well, this is a walking or swimming dude after all, not a ship in the void. He's moving in a world where friction is preponderant. Acceleration is only simulated, in the sense that you cannot change to arbitrary speed values in no time. On ordinary ground, accelerations seem limited to about 1000 to 15000 units/secondâ–“.
The game messages sent by the Quake server are exactly the same as the ones used in the demo files (.DEM), so they will not be repeated here. Look at the DEM file specifications.
A single Quake server message contains exactly the same informations as those contained in a .DEM file block of messages. That is rather logical, since they certainly didn't create a new format just for the .DEM files, but used the network protocol instead.
Let's just recall those useful orders:
Code | Message | Informations | Explanations |
---|---|---|---|
0x19 | Set signon state | state = ReadChar() | This sets the signon state, a state variable
that is used to guide the intialisation of a client.
1 = PRESPAWN (prepare for entity spawning). 2 = INIT LIGHT EFFECTS 3 = START 3D RENDERING (run the game). |
0x80-0xFF | Entity update | Based on mask. Very complicated. | This is the standard message that updates the position, angles, frame and models of a given entity. |
Here are some examples of how a Quake server and a Quake client interact. They sumarise an actual game session, observed by intercepting the game packets.
Those messages are sent to and from the PUBLIC UDP PORT of the server (which is 26000 by default).
Client sends a Request Informations message, with game name QUAKE. Server sends a Give Informations message, with indications on the current map, number of players...
Note: Those messages can be sent to interrogate a given Quake server, without really disturbing it. Of course, if you flood a Quake server with such messages, it might slow down.
Those messages are sent to and from the PUBLIC UDP PORT of the server (which is 26000 by default).
Client sends a Connect message, with game name QUAKE. Server sends an Accept message, containing the identifier of a PERSONAL UDP PORT for the client. Client now expects server message coming from the PERSONAL UDP PORT
Warning: after it replied with an Accept message, the game server will only use a PERSONAL UDP PORT to communicate with a given client. The server PUBLIC UDP PORT is not used anymore to talk to that client. It can only be used by clients that are not connected yet.
Server sends a big message that contains: - the server banner "VERSION 1.01 SERVER (21456 CRC)" - a server info message (code 0x0B) with map name, and precached models and sounds - an indication that client should move to PRESPAWN state. Client sends a No Operation (code 0x01) Client sends a console command (code 0x04) "prespawn", indicating prespawn is finished. Server sends a big message that contains order to spawn static sounds and static entities - For all the static entities described in the .BSP file, One "Spawn Static entity" order, and possibly a Static Sound order. Client sends a player information message: - console order "name PLAYER\n" (where PLAYER is the player name) - console order "color 0 0\n" (shirt and pants colors) - console order "spawn " to indicate the client is ready to play. Server sends a big message: - For each possible player in the game (including those not connected): a set of orders UPDATE NAME, UPDATE FRAGS, UPDATE COLORS - For each of the 64 light style: a SET LIGHT STYLE message. - an UPDATE STATE message for total and found monsters and secrets - a SET ANGLE order, to orient the player's view - a CLIENT DATA order, fix the status bar display - an order to move to Start 3d Rendering state. - a last update NAMe, FRAGS, and COLORS, for the client. Client sends a console message "begin".
Every 50 milliseconds, Server sends an update message, that contains: - an indication of the game time - a CLIENT DATA order, to fix the status bar display (mostly useless) - an UPDATE ENTITY order for each entity possibly in sight if the entity has not changed, only minimal informations will be sent. Each time the player moves, Client sends an update message containing: - a movement order.
These are only partial informations, but that should be enough for a start.
Here is a simplified connection procedure, to register into a Quake server.
"prespawn". (there is no return character at the end)
"name player\n" ('\n' is the return character) "color 0 0\n" ('\n' is the return character) "spawn " (there is white space at the end).
"begin" (there is no return character at the end).
How to send movement orders:
How to send orders on the server console:
A client receives no update for the entities not in sight, so you cannot cheat and make some radar that sees through walls. BUT you can of course make a simple bot that will turn immediatly toward any enemy coming in his back. And will of course avoid any rockets, or even straffe out of the line of sight of any hostile player.
Beware that the client must rely on himself alone to guess the geometry of the room he is in. The best is of course to load the .BSP for the level, and to keep track of the position of every entities, so as to know which paths are possible, and which are not. But you will soon discover that the .BSP per se is not enough to code a good behavior for a Bot, because there is no trace of the links between the rooms.
Here is the cleanest way to leave the server: